#include "MarsPrecompile.h"

#include "MarsRenderer.h"

#include "MarsMaterialMgr.h"
#include "MarsShaderMgr.h"
#include "MarsRenderTarget.h"

using namespace Graphics;

Renderer* Renderer::ms_pRenderer = 0;

void Renderer::Destroy()
{
	SAFE_DELETE( ms_pRenderer );
}

Renderer::Renderer() 
	: m_driverType( D3D_DRIVER_TYPE_NULL )
	, m_featureLevel( D3D_FEATURE_LEVEL_11_0 )
	, m_pD3dDevice( 0 )
	, m_pD3dContext( 0 )
	, m_pSwapChain( 0 )
	, m_pBackBufferTargetView( 0 )
	, m_iWindowWidth( 0 )
	, m_iWindowHeight( 0 )
{
}

Renderer::~Renderer()
{
	Shutdown();
}

bool Renderer::Initialize( HINSTANCE hInstance, HWND hwnd )
{
	m_hInstance = hInstance;
	m_hwnd = hwnd;

	RECT dimensions;
	GetClientRect( hwnd, &dimensions );

	m_iWindowWidth = dimensions.right - dimensions.left;
	m_iWindowHeight = dimensions.bottom - dimensions.top;

	D3D_DRIVER_TYPE driverTypes[] =
	{
		D3D_DRIVER_TYPE_HARDWARE, 
		D3D_DRIVER_TYPE_WARP,
		D3D_DRIVER_TYPE_REFERENCE, 
		D3D_DRIVER_TYPE_SOFTWARE
	};

	unsigned int totalDriverTypes = ARRAYSIZE( driverTypes );

	D3D_FEATURE_LEVEL featureLevels[] =
	{
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0
	};

	unsigned int totalFeatureLevels = ARRAYSIZE( featureLevels );

	DXGI_SWAP_CHAIN_DESC swapChainDesc;
	ZeroMemory( &swapChainDesc, sizeof( swapChainDesc ) );
	swapChainDesc.BufferCount = 1;
	swapChainDesc.BufferDesc.Width = m_iWindowWidth;
	swapChainDesc.BufferDesc.Height = m_iWindowHeight;
	swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
	swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
	swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	swapChainDesc.OutputWindow = hwnd;
	swapChainDesc.Windowed = true;
	swapChainDesc.SampleDesc.Count = 1;
	swapChainDesc.SampleDesc.Quality = 0;

	unsigned int creationFlags = 0;

#ifdef _DEBUG
	creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	HRESULT result;
	unsigned int driver = 0;

	for ( driver = 0; driver < totalDriverTypes; ++driver )
	{
		result = D3D11CreateDeviceAndSwapChain( 
			0, 
			driverTypes[driver], 
			0, 
			creationFlags,
			featureLevels, 
			totalFeatureLevels,
			D3D11_SDK_VERSION, 
			&swapChainDesc, 
			&m_pSwapChain,
			&m_pD3dDevice, 
			&m_featureLevel, 
			&m_pD3dContext );

		if ( SUCCEEDED( result ) )
		{
			m_driverType = driverTypes[driver];
			break;
		}
	}

	if ( FAILED( result ) )
	{
		DXTRACE_MSG( "Failed to create the Direct3D device!" );
		Shutdown();
		return false;
	}

	ID3D11Texture2D* backBufferTexture;

	result = m_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&backBufferTexture );

	if ( FAILED( result ) )
	{
		DXTRACE_MSG( "Failed to get the swap chain back buffer!" );
		Shutdown();
		return false;
	}

	result = m_pD3dDevice->CreateRenderTargetView( backBufferTexture, 0, &m_pBackBufferTargetView );

	if ( backBufferTexture )
		backBufferTexture->Release( );

	if ( FAILED( result ) )
	{
		DXTRACE_MSG( "Failed to create the render target view!" );
		Shutdown();
		return false;
	}

	D3D11_TEXTURE2D_DESC depthTexDesc;
	ZeroMemory( &depthTexDesc, sizeof( depthTexDesc ) );
	depthTexDesc.Width = m_iWindowWidth;
	depthTexDesc.Height = m_iWindowHeight;
	depthTexDesc.MipLevels = 1;
	depthTexDesc.ArraySize = 1;
	depthTexDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthTexDesc.SampleDesc.Count = 1;
	depthTexDesc.SampleDesc.Quality = 0;
	depthTexDesc.Usage = D3D11_USAGE_DEFAULT;
	depthTexDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	depthTexDesc.CPUAccessFlags = 0;
	depthTexDesc.MiscFlags = 0;

	result = m_pD3dDevice->CreateTexture2D( &depthTexDesc, NULL, &m_pDepthTexture );

	if ( FAILED( result ) )
	{
		DXTRACE_MSG( "Failed to create the depth texture!" );
		Shutdown();
		return false;
	}

	// Create the depth stencil view
	D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
	ZeroMemory( &descDSV, sizeof( descDSV ) );
	descDSV.Format = depthTexDesc.Format;
	descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
	descDSV.Texture2D.MipSlice = 0;

	result = m_pD3dDevice->CreateDepthStencilView( m_pDepthTexture, &descDSV, &m_pDepthStencilView );

	if ( FAILED( result ) )
	{
		DXTRACE_MSG( "Failed to create the depth stencil target view!" );
		Shutdown();
		return false;
	}

	m_pD3dContext->OMSetRenderTargets( 1, &m_pBackBufferTargetView, m_pDepthStencilView );

	D3D11_VIEWPORT viewport;
	viewport.Width = static_cast< float >( m_iWindowWidth );
	viewport.Height = static_cast< float >( m_iWindowHeight );
	viewport.MinDepth = 0.0f;
	viewport.MaxDepth = 1.0f;
	viewport.TopLeftX = 0.0f;
	viewport.TopLeftY = 0.0f;

	m_pD3dContext->RSSetViewports( 1, &viewport );

	if ( !MaterialMgr::Instance().Initialize() )
	{
		Shutdown();
		return false;
	}

	if ( !ShaderMgr::Instance().Initialize() )
	{
		Shutdown();
		return false;
	}

	return true;
}

void Renderer::Shutdown()
{
	ShaderMgr::Destroy();
	MaterialMgr::Destroy();


	SAFE_RELEASE( m_pBackBufferTargetView );
	SAFE_RELEASE( m_pSwapChain );
	SAFE_RELEASE( m_pD3dContext );

#if defined( DEBUG ) || defined( _DEBUG )
	ID3D11Debug* d3dDebug = NULL;
	if ( SUCCEEDED( m_pD3dDevice->QueryInterface( __uuidof( ID3D11Debug ), ( void** )&d3dDebug ) ) )
	{

		d3dDebug->ReportLiveDeviceObjects( D3D11_RLDO_DETAIL );
		d3dDebug->Release();
	}
#endif

	SAFE_RELEASE( m_pD3dDevice );
}

void Renderer::ClearRenderTarget( const float colorRGBA[ 4 ] )
{
	m_pD3dContext->ClearRenderTargetView( m_pBackBufferTargetView, colorRGBA );
}

void Renderer::ClearDepthStencil( float fDepthValue, uint8 iStencilValue )
{
	m_pD3dContext->ClearDepthStencilView( m_pDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, fDepthValue, iStencilValue );
}

void Renderer::Present()
{
	m_pSwapChain->Present( 0, 0 );
}

void Renderer::PushRenderTarget( RenderTarget* pRenderTarget )
{
	m_renderTargets.push( pRenderTarget );
	pRenderTarget->Bind();
}

RenderTarget* Renderer::PopRenderTarget()
{
	MARS_ASSERT( m_renderTargets.size() != 0 );

	RenderTarget* pResult = m_renderTargets.top();
	m_renderTargets.pop();

	if ( m_renderTargets.size() == 0 )
	{
		m_pD3dContext->OMSetRenderTargets( 1, &m_pBackBufferTargetView, m_pDepthStencilView );

		D3D11_VIEWPORT viewport;
		viewport.Width = static_cast< float >( m_iWindowWidth );
		viewport.Height = static_cast< float >( m_iWindowHeight );
		viewport.MinDepth = 0.0f;
		viewport.MaxDepth = 1.0f;
		viewport.TopLeftX = 0.0f;
		viewport.TopLeftY = 0.0f;

		m_pD3dContext->RSSetViewports( 1, &viewport );
	}
	else
	{
		m_renderTargets.top()->Bind();
	}

	return pResult;
}